<cfcomponent>
	<!---
	CF-STORM - Super Tiny ORM
	Beau Holton
	--->
	
	<cfset variables.objectname = ""/>
	<cfset variables.attributelist = ""/>
	<cfset variables.attributes = Structnew()/>
	<cfset variables.pk = ""/>
	<cfset variables.pkdefault = 0/>
	<cfset variables.seq = ""/>
	<cfset variables.dsn = ""/>
	<cfset variables.isLoaded = false/>
	
	<cffunction name="init" access="public">
		<cfargument name="dsn" required="true"/>
		<cfargument name="objectname" required="true"/>
		<cfargument name="attributes" required="true"/>
		<cfargument name="pk" required="true"/>
		<cfargument name="pkdefault" required="true"/>
		<cfargument name="seq" required="true"/>
		<cfset variables.objectname = arguments.objectname/>
		<cfset variables.attributeList = StructKeyList(arguments.attributes)/>
		<cfset variables.attributes = arguments.attributes />
		<cfset variables.pk = arguments.pk/>
		<cfset variables.pkdefault = arguments.pkdefault/>
		<cfset variables.seq = arguments.seq/>
		<cfset variables.dsn = arguments.dsn/>
		<cfset this["#variables.pk#"] = variables.pkdefault/>
		<cfreturn this/> 
	</cffunction>
		
	<cffunction name="getValue" access="private">
		<cfargument name="attribute" required="true"/>
		<cfset var value = ""/>
		<cfif StructKeyExists(this,arguments.attribute)>
			<cfset value = this["#arguments.attribute#"]/>
		<cfelse>
			<cfthrow message="value #arguments.attribute# is undefined in #variables.objectname#" detail="undefined value in storm object" type="storm.nullvalue"/>
		</cfif>
		<cfreturn value/>
	</cffunction>
	
	<cffunction name="isNull" access="public">
		<cfargument name="attribute" required="true"/>
		<cfset var isnull = false/>
		<cfif variables.isLoaded and NOT variables.attributes[arguments.attribute].required and NOT StructKeyExists(this,arguments.attribute)>
			<cfset isnull = true/>
		<cfelseif NOT variables.attributes[arguments.attribute].required and NOT StructKeyExists(this,arguments.attribute) and StructKeyExists(variables.attributes[arguments.attribute],"value")>
			<cfset isnull = true/>
		</cfif>
		<cfreturn isnull/>
	</cffunction>
	
	<cffunction name="loadFromQuery" access="public" output="true">
		<cfargument name="query" required="true" type="query"/>
		<cfargument name="row" required="false" default="1"/>
		<cfset var attribute = "" />
		<cfloop list="#variables.attributeList#" index="attribute" delimiters=",">
			<cfif ListContainsNoCase(Lcase(arguments.query.ColumnList),attribute)>
				<cfset this["#attribute#"] = arguments.query["#attribute#"][arguments.row] />
				<cfset variables.attributes["#attribute#"].value = arguments.query["#attribute#"]/>
			</cfif>
		</cfloop>
		<cfset variables.isLoaded = true/>

	</cffunction>
		
	<cffunction name="load" access="public" output="true">
		<cfset var getdata = "" />
			<cftry>
				<cfquery name="getdata" datasource="#variables.dsn#">
					select
						#variables.attributeList#
					from
						#variables.objectname# 
					where
						#variables.pk# = <cfqueryparam cfsqltype="cf_sql_numeric" value="#getValue(variables.pk)#" />
				</cfquery>
				<cfif NOT getdata.recordcount>
					<cfthrow type="cfstorm.nomatchingrecord" message="No record was found matching the supplied primary key" />
				</cfif>
				<cfset loadFromQuery(getdata) />
			<cfcatch type="database">
				database error
				<cfoutput>#cfcatch.queryError#</cfoutput><br />
				<cfoutput>#cfcatch.sql#</cfoutput>
			</cfcatch>
			</cftry>
		
	</cffunction>
	
	<cffunction name="delete" access="public" output="false">
		<cfset var getdata = "" />
		<cfset var pkval = getValue(variables.pk)/>
		<cfif pkval neq variables.pkdefault>
			<cftry>
				<cfquery name="getdata" datasource="#variables.dsn#">
					delete from
						#variables.objectname# 
					where
						#variables.pk# = <cfqueryparam cfsqltype="cf_sql_numeric" value="#pkval#" />
				</cfquery>
				<cfset variables.isLoaded = false />
			<cfcatch type="database">
				database error
				<cfoutput>#cfcatch.queryError#</cfoutput><br />
				<cfoutput>#cfcatch.sql#</cfoutput>
			</cfcatch>
			</cftry>
		</cfif>	
	</cffunction>
	
	<cffunction name="save" access="public">
		<cfif NOT variables.isLoaded>
			<cfset saveByInsert()/>
		<cfelse>
			<cfset saveByUpdate()/>
		</cfif>
	</cffunction>
	
	<cffunction name="saveByInsert" access="private" output="true">
		<cfset var putdata = "" />
		<cfset var attrib = ""/>
		<cfset var pkval = getValue(variables.pk)/>
		<cfset var counter = 0/>
		<cfset var insertlist = variables.attributelist/>
		<cfloop list="#variables.attributelist#" index="attrib">
			<cfif NOT StructKeyExists(this,attrib) and ((variables.attributes[attrib].required and variables.attributes[attrib].hasdefault) or NOT variables.attributes[attrib].required)>
				<cfset insertlist = listdeleteat(insertlist,listfind(insertlist,attrib))/>
				
			</cfif>
		</cfloop>
		<cftry>
			<cfif pkval eq variables.pkdefault and variables.seq neq "">
				<cfset this["#variables.pk#"] = getNextSequence()/>
			</cfif>
			<cfquery name="putdata" datasource="#variables.dsn#">
				insert into
					#variables.objectname# 
				(#insertlist#)
				values
				(
				<cfloop list="#insertlist#" index="attrib">
					<cfif isNull(attrib)>
						<cfqueryparam value="" null="yes"/>
					<cfelseif variables.attributes["#attrib#"].type eq "number">
						<cfqueryparam cfsqltype="cf_sql_numeric" value="#getValue(attrib)#"/><cfif counter neq ListLen(insertlist)-1>,</cfif>
					<cfelseif variables.attributes["#attrib#"].type eq "date">
						<cfqueryparam cfsqltype="cf_sql_timestamp" value="#getValue(attrib)#"/><cfif counter neq ListLen(insertlist)-1>,</cfif>	
					<cfelse>
						<cfqueryparam cfsqltype="cf_sql_varchar" value="#getValue(attrib)#"/><cfif counter neq ListLen(insertlist)-1>,</cfif>
					</cfif>
					<cfset counter = counter + 1/>
				</cfloop>	
				)
			</cfquery>
			<cfset variables.isLoaded = true/>
		<cfcatch type="database">
			database error 
			<cfoutput>#cfcatch.queryError#</cfoutput><br />
			<cfoutput>#cfcatch.sql#</cfoutput>
			<cfdump var="#cfcatch#"/><cfabort>
		</cfcatch>
		</cftry>
	</cffunction>
	
	<cffunction name="saveByUpdate" access="private">
		<cfset var putdata = "" />
		<cfset var attrib = ""/>
		<cfset var pkval = getValue(variables.pk)/>
		<cfset var counter = 0/>
		<cfset var dirtyList = ""/>
		<cfloop list="#variables.attributeList#" index="attrib">
			<cfif (NOT StructKeyExists(variables.attributes,attrib) and StructKeyExists(this,attrib)) or getValue(attrib) neq variables.attributes["#attrib#"].value>
				<cfif dirtyList eq "">
					<cfset dirtyList = attrib/>
				<cfelse>
					<cfset dirtyList = dirtyList & "," & attrib/>
				</cfif>
			</cfif>
		</cfloop>
		<cfif dirtyList neq "">
			<cftry>
				<cfquery name="putdata" datasource="#variables.dsn#">
					update
						#variables.objectname# 
					set
					<cfloop list="#dirtyList#" index="attrib">
						<cfif attrib neq variables.pk>
							<cfif counter neq 0>,</cfif>
							<cfif isNull(attrib)>
								#attrib# = <cfqueryparam value="" null="yes"/>
							<cfelseif variables.attributes["#attrib#"].type eq "number">
								#attrib# = <cfqueryparam cfsqltype="cf_sql_numeric" value="#getValue(attrib)#"/>
							<cfelseif variables.attributes["#attrib#"].type eq "date">
								#attrib# = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#getValue(attrib)#"/>
							<cfelse>
								#attrib# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#getValue(attrib)#"/>
							</cfif>
							
							<cfset counter = counter + 1/>
						</cfif>
					</cfloop>	
					where
						#variables.pk# = <cfqueryparam cfsqltype="cf_sql_numeric" value="#getValue(variables.pk)#" />
				</cfquery>
			<cfcatch type="database">
				database error<cfdump var="#cfcatch#"/><cfabort>
				<cfoutput>#cfcatch.queryError#</cfoutput><br />
				<cfoutput>#cfcatch.sql#</cfoutput>
			</cfcatch>
			</cftry>
		</cfif>
	</cffunction>
	
	<cffunction name="revert" access="public">
		<cfif variables.isLoaded>
			<cfloop list="#variables.attributeList#" index="attrib">
				<cfset this["#attrib#"] = variables.attributes["#attrib#"].value />
			</cfloop>
		<cfelse>
			<cfloop list="#variables.attributeList#" index="attrib">
				<cfset StructDelete(this,"#attrib#") />
			</cfloop>
		</cfif>
	</cffunction>
	
	<cffunction name="getNextSequence" access="private">
		<cfset var nextSeq = 0/>
		<cfif variables.seq neq "">
			<cftry>
				<cfquery name="nextSeq" datasource="#variables.dsn#">
					select #variables.seq#.nextval from dual
				</cfquery>
			<cfcatch type="database">
				#cfcatch.Message# - #cfcatch.Detail#
			</cfcatch>
			</cftry>		
		<cfelse>
			<cfthrow message="This object has no sequence" detail="The object was not initialized with a sequence, check the storm.cfc for your app" type="cfstorm.sequence"/>
		</cfif>
		<cfreturn nextSeq.nextval/>
	</cffunction>
	
	<cffunction name="validate" access="public">
		<cfargument name="errorStruct" required="false" default="#StructNew()#"/>
		<cfset var error = 0/>
		<cfset var null = ""/>
		<cfset StructDelete(this,"validationErrors")/>
		<cfset variables.hasErrors = false/>
		<cfloop list="#variables.attributelist#" index="attrib">
			<cfif StructKeyExists(this,attrib)>
				<cfset null = false/>
				<cfif this["#attrib#"] eq "" and variables.attributes["#attrib#"].required eq true and variables.attributes["#attrib#"].hasdefault eq false>
					<cfset arguments.errorStruct = insertError(arguments.errorStruct,attrib,"#variables.objectname#.#attrib# is a required value.")/>
				<cfelseif this["#attrib#"] eq "" and variables.attributes["#attrib#"].required eq false>
					<!--- null - bypass type check--->
					<cfset null = true/>
				<cfelseif variables.attributes["#attrib#"].type eq "number">
					<!--- Type Check --->
					<cfif NOT isNumeric(this["#attrib#"])>
						<cfset arguments.errorStruct = insertError(arguments.errorStruct,attrib,"#variables.objectname#.#attrib# must be a number.")/>
					</cfif>
				<cfelseif variables.attributes["#attrib#"].type eq "date">
					<cfif NOT isDate(this["#attrib#"])>
						<cfset arguments.errorStruct = insertError(arguments.errorStruct,attrib,"#variables.objectname#.#attrib# must be a date.")/>
					</cfif>
				</cfif>
				<!--- Length Check --->
				<cfif NOT null and (variables.attributes["#attrib#"].type eq "number" or variables.attributes["#attrib#"].type eq "char") and len(this["#attrib#"]) gt variables.attributes["#attrib#"].length>
					<cfset arguments.errorStruct = insertError(arguments.errorStruct,attrib,"#variables.objectname#.#attrib# cannot be longer than" & variables.attributes["#attrib#"].length & ".")/>
				</cfif>
			<cfelse>
				<cfif variables.attributes["#attrib#"].required eq true and variables.attributes["#attrib#"].hasdefault eq false>
					<cfset arguments.errorStruct = insertError(arguments.errorStruct,attrib,"#variables.objectname#.#attrib# is a required value.")/>
				</cfif>
			</cfif>
		</cfloop>

		<cfif StructCount(errorStruct)>
			<cfset variables.hasErrors = true/>
			<cfset StructInsert(this,"validationErrors",arguments.errorStruct)/>
		</cfif>
		<cfreturn/>
	</cffunction>
	
	<cffunction name="insertError" access="private">
		<cfargument name="errorStruct" required="true"/>
		<cfargument name="attrib" required="true"/>
		<cfargument name="errorMessage" required="true"/>
		<cfif StructkeyExists(arguments.errorStruct,arguments.attrib)>
			<cfset ArrayAppend(arguments.errorStruct["#arguments.attrib#"],arguments.errorMessage)/> 
		<cfelse>
			<cfset StructInsert(arguments.errorStruct,attrib,ArrayNew(1))/>
			<cfset ArrayAppend(arguments.errorStruct["#arguments.attrib#"],arguments.errorMessage)/> 
		</cfif>
		<cfreturn arguments.errorStruct/>
	</cffunction>
	
	<cffunction name="addError" access="public">
		<cfargument name="attrib" required="true"/>
		<cfargument name="errorMessage" required="true"/>
		<cfset var errorStruct = 0/>
		<cfif NOT StructKeyExists(this,"validationErrors")>
			<cfset errorStruct = StructNew()/>
			<cfset StructInsert(errorStruct,arguments.attrib,ArrayNew(1))/>
			<cfset ArrayAppend(errorStruct["#arguments.attrib#"],arguments.errorMessage)/> 
			<cfset StructInsert(this,"validationErrors",errorStruct)/>
		<cfelse>
			<cfif StructkeyExists(this["validationErrors"],arguments.attrib)>
				<cfset ArrayAppend(this["validationErrors"]["#arguments.attrib#"],arguments.errorMessage)/> 
			<cfelse>
				<cfset StructInsert(this["validationErrors"],arguments.attrib,ArrayNew(1))/>
				<cfset ArrayAppend(this["validationErrors"]["#arguments.attrib#"],arguments.errorMessage)/> 
			</cfif>
		</cfif>
		<cfset variables.hasErrors = true/>
	</cffunction>
	
	<cffunction name="hasErrors" access="public">
		<cfreturn variables.hasErrors/>
	</cffunction>
	
	<cffunction name="getQuery" access="public">
		<cfargument name="orderby" required="false" default="none"/>
		<cfset var objectquery = 0/>
		<cfset objectfield = 0/>
		<cftry>
			<cfquery name="objectquery" datasource="#variables.dsn#">
				select
					#variables.attributelist#
				from
					#variables.objectname#	
				
				where
				1=1
				<cfloop list="#variables.attributeList#" index="attrib">
					<cfif StructkeyExists(arguments,attrib)>
						<cfif variables.attributes["#attrib#"].type eq "number">
							and #attrib# = <cfqueryparam cfsqltype="cf_sql_numeric" value="#arguments[attrib]#"/>
						<cfelseif variables.attributes["#attrib#"].type eq "date">
							and #attrib# = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#arguments[attrib]#"/>	
						<cfelse>
							and #attrib# = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments[attrib]#"/>
						</cfif>
					</cfif>
				</cfloop>
				<cfif arguments.orderby neq "none">
					order by #arguments.orderby#
				</cfif>	
			</cfquery>
		<cfcatch type="database">
			<cfthrow type="cfstorm.queryerror" detail="there was a problem with the getquery function for the #variables.objectname# object"/>
		</cfcatch>
		</cftry>
		<cfreturn objectquery/>
	</cffunction>
	
	<cffunction name="merge" hint="I merge another storm object or struct into this." access="public" returntype="void">
		<cfargument name="struct" hint="I am the other storm object or struct" required="yes" type="any"/>
		<cfset var attrib = 0 />
		
		<cfloop list="#variables.attributeList#" index="attrib">
			<cfif StructKeyExists(arguments.struct,attrib)>
				<cfset this[attrib] = arguments.struct[attrib]/>
			</cfif>
		</cfloop>
	</cffunction>
	
	<cffunction name="getDebugData" access="public">
		<cfreturn variables />
	
	</cffunction>
</cfcomponent>